import {Game, game, log } from "cc";
import { Message } from "../manager/Message";
import { GameConst, SocketConst } from "../AppConst";
import { ByteArray } from "./buffer/ByteArray";
import { TimeTools } from "../tools/TimeTools";
import { Singleton } from "../manager/Singleton";

const ReconnectionInterval: number = 5000;
const HeartbeatInterval: number = 3000; // 心跳间隔
const HeartbeatTIMEOUT: number = 5000; // 心跳超时 

export class Network extends Singleton {

    private _webSocket: any = null;
    private _cmdlist = new Map();
    private _connect_count: number = 0;
    private _timer: any = 0;

    private _isbackground: boolean = false;//是否进入后台

    /** 验证token回调函数 返回验证token 协议号 */
    private _setTokenCode: number = 0;
    /** 操作成功 */
    static Success: number = 0;
    static Heartbeat = 1 << 7;//心跳包
    static Data = 0 << 0;//数据包

    /**
     * 是否派发事件
     */
    isDispatchEvent: boolean = false
    //游戏错误代码强制退出大厅
    private err_code_list: number[] = [114, 115]

    protected init(): void {
        game.on(Game.EVENT_SHOW, this.onShowhandler, this)
        game.on(Game.EVENT_HIDE, this.onHidehandler, this)
    }



    setTokenCode(value: number) {
        this._setTokenCode = value;
    }
    /** 退出游戏 */
    public exitGame(code: number, param: any = {}):Promise<void>{
        return new Promise(resolve=>{
            Network.getInstance().send(code, param, (data) => {
                if (data.code == 0) {
                    console.log("------>game edit completed " + code)
                }
                Network.getInstance().removehandle(code);
                resolve();
            })
        })
        
    }

    registhandle(cmd: string | number, methods?: Function) {
        const handle = {
            'proto': cmd,
            'handle': methods,
        }
        let list = this._cmdlist.get(cmd)
        if (!list) {
            list = []
            this._cmdlist.set(cmd, list);
        }
        list.push(handle)
    }

    callAllBack(cmd: number | string, value: any) {
        let list: any[] = this._cmdlist.get(cmd) || []
        for (let i = 0; i < list.length; i++) {
            list[i]?.handle(value, cmd);
        }
    }
    /**
     * 移除监听的网络回调函数，methods==null时会删除当前协议全部监听，methods！=null时只会删除当前协议对应的函数监听
     * @param cmd 
     * @param methods 
     */
    removehandle(cmd: string | number, methods?: Function) {
        if (this._cmdlist.has(cmd)) {
            let list: any[] = this._cmdlist.get(cmd)
            if (list.length > 0) {
                if (methods) {
                    for (let i = 0; i < list.length; i++) {
                        if (methods.name == list[i].handle.name) {
                            list.splice(i, 1)
                            break
                        }
                    }
                } else {
                    list = []; 
                }

            }
            if (list.length <= 0) {
                this._cmdlist.delete(cmd); 
            }
        }
    }
    //重置自动连接
    reSetConnect() { 
        this.onCloseUI() 
        if(this._isbackground)this.autoConnectToServer(true);  
    }
    private onCloseUI(){
        this.close();
        this._connect_count = 0;
        this._socket_connect=false  
    }
    private _connected:boolean=false;
    private _socket_connect:boolean=false;   
    /**  
     * 链接服务器 
     */
    async autoConnectToServer(showLoading?:boolean) {
        while (true) {  
            if(this._isbackground)break   
            if (!this.isOpen) {   
                this._connected=true 
                Message.send(SocketConst.CONNECT_STATUS,showLoading)
                const wsURL = localStorage.getItem("gate")  
                console.log("gate > ",wsURL)
                console.log(wsURL + ' -> try connect count = %d', this._connect_count);
                const code = await this.Socket(wsURL);
                console.log("========autoConnectToServer============",code) 
                if (code == '200') { 
                    this._connected=false;  
                    this._connect_count = 0; 
                    this._socket_connect=true  
                    
                    Message.send(SocketConst.CONNECT_STATUS,showLoading,false)  
                    // Toast.HideTips();
                    this.ping();
                    console.log('hall server: connect success code = %s', code);
                    Message.send(SocketConst.CONNECTED,true)
                    // await TimeUtil.Sleep(500)
                }else{ 
                    Message.send(SocketConst.CONNECT_STATUS,showLoading,true)
                    this._connect_count++;
                    if (this._connect_count >= 5) {
                        this._connect_count = 0; 
                        // Toast.showOrHideNetLoading(true); 
                        Message.send(SocketConst.DISCONNECTED)
                        // break;
                    } 
                }

            } else {
                if(this._connected)Message.send(SocketConst.CONNECT_STATUS,showLoading,false)
            }
            await TimeTools.Sleep(1)
        }
 
    }

    private _heartbeatOutTime:number=0;

    send(proto: number, msg: any, callback?: Function):Promise<void> {
        return new Promise((resove,reject)=>{
            if (!this.isOpen) {
                console.warn("connected server is fail.")
                resove()
                return  
            }
            if (!msg) msg = {}
            if (callback) this.registhandle(proto, callback);//注册回调
            let buffer: ByteArray = new ByteArray();
            let type: number = proto <= 0 ? 1 : 0;
            if (type == 0) {
                buffer.writeByte(Network.Data)
                buffer.writeInt(proto)
                // msg.name="中国"//测试 
                buffer.writeUnsignedUTF2(JSON.stringify(msg)) 
                
            } else {
                buffer.writeByte(Network.Heartbeat)
            }
            this._lastSendTime = Date.now();
            console.log(`客户端发送数据 【${proto}】>> ` + JSON.stringify(msg))
            this._webSocket.send(buffer.buffer);
            resove(); 
        }) 
    }

    close() {
        this._timer && clearInterval(this._timer);
        this._timer = null;
        if (this._webSocket) {
            this._webSocket.close();
            this._webSocket.onopen = null;
            this._webSocket.onmessage = null;
            this._webSocket.onclose = null;
            this._webSocket.onerror = null;
            this._webSocket = null;
        }

    }

    private Socket(url?: string): Promise<string> {
        return new Promise((resolve) => {
            this._webSocket = new WebSocket(url);     
            this._webSocket.binaryType = "arraybuffer"
            this._webSocket.onopen = () => {
                console.log("服务器链接成功")
                resolve("200");  
            };
            this._webSocket.onmessage = this.message.bind(this);
            this._webSocket.onclose = (err) => {
                console.log("服务器链接失败，远程服务器关闭")
                this.close()
                resolve("401"); 
            };
            this._webSocket.onerror = (err) => {
                console.log("链接远程服务器错误")
                this.close()
                resolve("404");
            }; 
        });
    } 
    get isOpen():boolean{
        return this._webSocket && this._webSocket.readyState == WebSocket.OPEN
    }
    get serTime() { return TimeTools.timestamp; }
    private _lastSendTime: number = 0;
    private _lastBackTime: number = 0;//上一次通信回来时间
    private message(event: any) {
        let buf: Uint8Array = new Uint8Array(event.data);
        let byteArray: ByteArray = new ByteArray(buf)
        const flag: number = byteArray.readUnsignedByte()
        if (flag == Network.Heartbeat) { 
            TimeTools.timestamp = byteArray.readUnsigneInt64();
            const cdt = Date.now()
            this._heartbeatOutTime=cdt 
            this._lastBackTime = cdt
            TimeTools.timeDifference = cdt - this._lastSendTime
            // console.log(`server timestamp  > ${TimeUtil.timestamp}`) 
        } else {
            let route = byteArray.readInt()
            console.error("收到 " + route + " 消息 >> ")
            let data_str = byteArray.readUnsignedUTF2();
            if (data_str.indexOf("{") >= 0 && data_str.indexOf("}")) {
                console.log("收到服务数据 >> " + data_str)
                let value = JSON.parse(data_str)
                let code = value.code || Network.Success
                if (code != Network.Success) {
                    if (route == this._setTokenCode) {
                        this.callAllBack(route, value);
                        return;
                    }
                    // warn(`收到数据【${route}】错误`) 
                }
                if (code > 0 && this.err_code_list.indexOf(code) >= 0) {
                    if(this.err_code_list.indexOf(code) >= 0)Message.send(SocketConst.ERROR, code,route, value)
                    return
                } 
                this.callAllBack(route, value);
                if (this.isDispatchEvent)Message.send(GameConst.GAME_SUB_MESSAGE, route, value)
               
            } else {
                console.error(`data parsing failed.`)
                this.callAllBack(route, {});
            }
        }
    }

    private ping() {
        if (this._timer) return;
        this._timer = setInterval(() => {
            if (this._webSocket && this._webSocket.readyState == WebSocket.OPEN) {
                this.send(0, {});
            } else {
                this.close()
            }
        }, HeartbeatInterval); 
    }
 
    private onShowhandler() {
        this._isbackground = false;
        this.autoConnectToServer();
        Message.send(GameConst.GAME_SHOW)
    }
 
    private onHidehandler() {
        this._isbackground = true;
        this.close();
        // director.emit('mini_close');
        Message.send(GameConst.GAME_HIDE)
    }

}

